package com.gcalsync.cal;

import java.util.TimeZone;

import javax.microedition.pim.Event;
import javax.microedition.pim.PIMException;
import javax.microedition.lcdui.Form;

import com.gcalsync.option.Options;
import com.gcalsync.cal.Recurrence;
import com.gcalsync.gcal.GCalClient;
import com.gcalsync.gcal.GCalEvent;
import com.gcalsync.phonecal.PhoneCalClient;
import com.gcalsync.store.Store;
import com.gcalsync.util.DateUtil;

/**
 * @author Thomas Oldervoll, thomas@zenior.no
 * @author $Author: batcage $
 * @version $Rev: 28 $
 * @date $Date: 2006-12-26 21:49:34 -0500 (Tue, 26 Dec 2006) $
 */
public class Merger {

    private PhoneCalClient phoneCalClient;
    private GCalClient gCalClient;
    private Options options;


    public Merger(PhoneCalClient phoneCalClient, GCalClient gCalClient) {
        this.phoneCalClient = phoneCalClient;
        this.gCalClient = gCalClient;
        this.options = Store.getOptions();
    }

    public Event copyToPhoneEvent(GCalEvent gCalEvent) throws PIMException {
        Event phoneEvent = phoneCalClient.createEvent();
        mergeIntoPhoneEvent(phoneEvent, gCalEvent);
        return phoneEvent;
    }

    public GCalEvent copyToGCalEvent(Event phoneEvent) {
        GCalEvent gCalEvent = new GCalEvent();
        mergeIntoGCalEvent(phoneEvent, gCalEvent);
        return gCalEvent;
    }

	public boolean compareEvents(Event phoneEvent, GCalEvent gCalEvent)
	{
		GCalEvent pe = copyToGCalEvent(phoneEvent);
		return (pe.id.equals(gCalEvent.id)
				&& pe.title.equals(gCalEvent.title)
				&& pe.note.equals(gCalEvent.note)
				&& (pe.recur != null && gCalEvent.recur != null && pe.recur.equals(gCalEvent.recur))
				&& pe.startTime == gCalEvent.startTime
				&& pe.endTime == gCalEvent.endTime);
	}

    public void mergeEvents(Event phoneEvent, GCalEvent gCalEvent, Form form) throws PIMException {
		boolean success;

		if (phoneEvent == null) {
            if (!gCalEvent.cancelled && options.download) {
                System.out.println("=> Inserting event " + gCalEvent.title + ", not present in phone");
                phoneEvent = copyToPhoneEvent(gCalEvent);

				if (form != null) form.append("Saving \"" + gCalEvent.title + "\"...");
				success = phoneCalClient.insertEvent(phoneEvent, gCalEvent.id);
                if (form != null) 
				{
					form.append(success ? "OK" : "ERR");
					form.append("\n");
				}

            }
            return;
        }

        Options options = Store.getOptions();
        Timestamps timestamps = Store.getTimestamps();
        long phoneEventChangeTime = phoneCalClient.getDateField(phoneEvent, Event.REVISION);
        boolean phoneEventHasChanged = phoneEventChangeTime > timestamps.lastSync;
        boolean gCalEventHasChanged = (gCalEvent.updated - timestamps.lastSync) > 2000; // 2 sec slack to allow for rounding etc

		//debug
		//System.out.println("PhoneDT=" + DateUtil.longToIsoDateTime(phoneEventChangeTime) + " GCalDT=" +
		//				   DateUtil.longToIsoDateTime(gCalEvent.updated) + " Upd=" + DateUtil.longToIsoDateTime(timestamps.lastSync));
		
        boolean phoneCalWonMerge = phoneEventHasChanged;
        boolean gCalWonMerge = gCalEventHasChanged;
        if (options.mergeStrategy == Options.GCAL_WINS_MERGE) {
            phoneCalWonMerge = false;
        } else if (options.mergeStrategy == Options.PHONE_WINS_MERGE) {
            gCalWonMerge = false;
        } else {
            // LAST_UPDATED_WINS_MERGE or something illega
            if (phoneCalWonMerge && gCalWonMerge) {
                // conflict, the last updated wins
                if (phoneEventChangeTime > gCalEvent.updated) {
                    gCalWonMerge = false;
                } else {
                    phoneCalWonMerge = false;
                }
            }
        }
        if (/*gCalWonMerge &&*/ options.download) {
            if (gCalEvent.cancelled) {
                System.out.println("=> Removing event " + gCalEvent.title + ", cancelled in GCal");
				if (form != null) form.append("Removing \"" + gCalEvent.title + "\" from phone...");
                success = phoneCalClient.removeEvent(phoneEvent);
            } else {
                System.out.println("=> Updating event " + gCalEvent.title + " in phone");
				if (form != null) form.append("Updating \"" + gCalEvent.title + "\" in phone...");
                mergeIntoPhoneEvent(phoneEvent, gCalEvent);
                success = phoneCalClient.updateEvent(phoneEvent);
            }

			if (form != null) 
			{
				form.append(success ? "OK" : "ERR");
				form.append("\n");
			}
        }
		//bug: if you do a full sync and the same event gets committed, the phone Cal wins
		//merge because it was written at a time after the GCalEvent was updated in Google
		// so, phoneCal tries to upload the event back to Google. I think it's a good idea
		// to let the Gcal win merge but not the other way around
		/*else if (phoneCalWonMerge && options.shouldUpdateGCal()) {
            System.out.println("=> Updating event " + gCalEvent.title + " in GCal");
			if (form != null) form.append("Updating \"" + gCalEvent.title + "\" in GCal...\n");
            mergeIntoGCalEvent(phoneEvent, gCalEvent);
            gCalClient.updateEvent(gCalEvent);
        }*/
    }

    private void mergeIntoPhoneEvent(Event phoneEvent, GCalEvent gCalEvent) {
        if (isSet(gCalEvent.title)) {
            phoneCalClient.setStringField(phoneEvent, Event.SUMMARY, gCalEvent.title);
        }
        if (isSet(gCalEvent.note)) {
            phoneCalClient.setStringField(phoneEvent, Event.NOTE, gCalEvent.note);
        }
        if (isSet(gCalEvent.location)) {
            phoneCalClient.setStringField(phoneEvent, Event.LOCATION, gCalEvent.location);
        }
		
        if (isSet(gCalEvent.startTime)) {
            phoneCalClient.setDateField(phoneEvent, Event.START, gCalEvent.startTime);
        }

		if (gCalEvent.isAllDay()) {
			//all-day PIM events are indicated by matching start and end times
			if (isSet(gCalEvent.startTime)) phoneCalClient.setDateField(phoneEvent, Event.END, gCalEvent.startTime);
		}
        else if (isSet(gCalEvent.endTime)) {
            phoneCalClient.setDateField(phoneEvent, Event.END, gCalEvent.endTime);
        }

        if (isSet(gCalEvent.reminder) && (gCalEvent.reminder >= 0)) {
            phoneCalClient.setIntField(phoneEvent, Event.ALARM, gCalEvent.reminder * 60);
        }

		try 
		{ 
			if (gCalEvent.recur != null && gCalEvent.recur.getRepeat() != null) 
				phoneEvent.setRepeat(gCalEvent.recur.getRepeat()); 
		}
		catch (Exception e) { System.out.println("Failed to copy repeat rule into phone cal, error=" + e.toString()); }
    }

    private boolean isSet(String value) {
        return (value != null) && value.length() > 0;
    }

    private boolean isSet(long value) {
        return value > 0;
    }

    private boolean isSet(int value) {
        return value >= 0;
    }

    private void mergeIntoGCalEvent(Event phoneEvent, GCalEvent gCalEvent) {
        String summary = phoneCalClient.getStringField(phoneEvent, Event.SUMMARY);
        if (isSet(summary)) {
            gCalEvent.title = summary;
        }
        String note = phoneCalClient.getStringField(phoneEvent, Event.NOTE);
        if (isSet(note)) {
            gCalEvent.note = note;
        }
        String location = phoneCalClient.getStringField(phoneEvent, Event.LOCATION);
        if (isSet(location)) {
            gCalEvent.location = location;
        }
        long startDate = phoneCalClient.getDateField(phoneEvent, Event.START);
        if (isSet(startDate)) {
            gCalEvent.startTime = startDate;
        }

        long endDate = phoneCalClient.getDateField(phoneEvent, Event.END);
        if (isSet(endDate)) {
            gCalEvent.endTime = endDate;
        }

		if (gCalEvent.isAllDay(Store.getOptions().uploadTimeZoneOffset))
		{
			//TODO: BlackBerry phones start all-day events on previous day (bug in BB OS?)
			//adjust for this behavior...not sure if other phones follow this behavior
			//so we'll have to add an option to enable the adjustment
			gCalEvent.startTime += 1000*60*60*24;
			gCalEvent.endTime += 1000*60*60*24;
		}

        int alarm = phoneCalClient.getIntField(phoneEvent, Event.ALARM);
        if (isSet(alarm)) {
            gCalEvent.reminder = alarm / 60;
        }

		try 
		{
			if (phoneEvent.getRepeat() != null)
				gCalEvent.recur = new Recurrence(phoneEvent.getRepeat(), startDate, endDate); 
		}
		catch (Exception e) { System.out.println("Failed to copy repeat rule into gCal, error=" + e.toString()); }
    }

}
